package phoneinterface;

import java.net.*;
import java.util.*;
import java.io.*;
import org.jdom.*;
import org.jdom.input.*;
import org.jdom.output.*;
import org.xml.sax.*;
import javax.xml.parsers.*;

/**
 * <p>Title: </p>
 * <p>Description: </p>
 * <p>Copyright: Copyright (c) 2002, 2005</p>
 * <p>Company: </p>
 * @author Tan, Khew, Fan, Lau
 * @version 1.02
 */

public class PhoneInterface {

  public static final String XML_PARSER = "org.apache.xerces.parsers.SAXParser";

  private String phoneName;
  private IP phoneIp;
  private int phonePort;

  /* permanent connection with phone */
  private Socket socket;
  private DataOutputStream outToServer;
  private DataInputStream inFromServer;

  private boolean connected;

  // wrapper constructor to preserve existing code
  public PhoneInterface(String phoneName, IP phoneIp, int phonePort) {
    this ( phoneName, phoneIp, phonePort, "commando.student.cs", 5000 ); 
  }

  public PhoneInterface(String phoneName, IP phoneIp, int phonePort, String ifHost, int ifPort ) {
    System.err.print ("Creating phone interface...");
    this.phoneName = phoneName;
    this.phoneIp = phoneIp;
    this.phonePort = phonePort;

    try {
      this.socket = new Socket(ifHost, ifPort);
      this.outToServer = new DataOutputStream(socket.getOutputStream());
      this.inFromServer = new DataInputStream(socket.getInputStream());
    }
    catch ( Exception e ) {
      System.err.println(e);
    }

    this.connected = false;
    System.err.println("Done");
  }

  //Aquire a phone
  public boolean AcquireResource() {
    System.err.print(phoneIp.getString() + ": Acquiring Resource...");

    Element root = new Element("AcquireResource");
    root.addContent(new Element("Resource").addContent(phoneIp.getString()));
    root.addContent(new Element("Name").addContent(phoneName));
    String sendingString = getSendingString(root);

    try {
      this.outToServer.writeBytes(sendingString + "\n\n");
      String reply = this.inFromServer.readLine();
      reply="<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + reply;
      Event event = this.interpreteXML(reply);
      if ( event.getType().equals(Event.RESOURCE_ACQUIRED_TYPE) ) {
        this.connected = true;
        System.err.println("Done");
        return true;
      }
      else {
        this.connected = false;
        System.err.println("Failed");
        return false;
      }
    }
    catch ( Exception e ) {
      this.connected = false;
      System.err.println("Failed");
      System.err.println(e);
      return false;
    }
  }

  //See if we're connected (resource aquired) or not
  public boolean isConnected() {
    return connected;
  }

  //Receive a message from the phone -- blocks until message is ready
  public Event getEvent() {
    try {
      String reply = inFromServer.readLine();
      reply = inFromServer.readLine();
      reply="<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + reply;
      Event event = this.interpreteXML(reply);
      return event;
    }
    catch ( Exception e ) {
      return null;
    }
  }

  //Turn handset on
  public boolean handsetOn() {
    System.err.println (phoneIp.getString() + ": Turning handset on");
    Element root = new Element("HandsetOn");
    String sendingString = getSendingString(root);
    try {
      outToServer.writeBytes(sendingString + "\n\n");
      return true;
    }
    catch ( Exception e ) {
      System.err.println(e);
      return false;
    }
  }

  //Turn handset off
  public boolean handsetOff() {
    System.err.println (phoneIp.getString() + ": Turning handset off");
    Element root = new Element("HandsetOff");
    String sendingString = getSendingString(root);
    try {
      outToServer.writeBytes(sendingString + "\n\n");
      return true;
    }
    catch ( Exception e ) {
      System.err.println(e);
      return false;
    }
  }

  //Start sending from a TN to given phoneIP
  public boolean startAudioSend(IP ip) {
    System.err.println (phoneIp.getString() + ": Starting audio send to " + ip.getString());
    Element root = new Element("StartAudioSend");
    root.addContent(new Element("DestDevice").addContent(ip.getString()));
    String sendingString = getSendingString(root);
    try {
      outToServer.writeBytes(sendingString + "\n\n");
      return true;
    }
    catch ( Exception e ) {
      System.err.println (e);
      return false;
    }
  }

  //Start listening from a TN (given phoneIP)
  public boolean startAudioReceive(IP ip) {
    System.err.println (phoneIp.getString() + ": Starting audio receive from " + ip.getString());
    Element root = new Element("StartAudioReceive");
    root.addContent(new Element("DestDevice").addContent(ip.getString()));
    String sendingString = getSendingString(root);
    try {
      outToServer.writeBytes(sendingString + "\n\n");
      return true;
    }
    catch ( Exception e ) {
      System.err.println (e);
      return false;
    }
  }

  //Stop sending to given phoneIP
  public boolean stopAudioSend(IP ip) {
    System.err.println (phoneIp.getString() + ": Stopping audio send to " + ip.getString());
    Element root = new Element("StopAudioSend");
    root.addContent(new Element("DestDevice").addContent(ip.getString()));
    String sendingString = getSendingString(root);
    try {
      outToServer.writeBytes(sendingString + "\n\n");
      return true;
    }
    catch ( Exception e ) {
      System.err.println(e);
      return false;
    }
  }

  //Stop receive from given phoneIP
  public boolean stopAudioReceive(IP ip) {
    System.err.print (phoneIp.getString() + ": Stopping audio receive from " + ip.getString() + "...");
    Element root = new Element("StopAudioReceive");
    root.addContent(new Element("DestDevice").addContent(ip.getString()));
    String sendingString = getSendingString(root);
    try {
      outToServer.writeBytes(sendingString + "\n\n");
      System.err.println("Success");
      return true;
    }
    catch ( Exception e ) {
      System.err.println("Fail");
      System.err.println(e);
      return false;
    }
  }

  //Turn lamp on
  public boolean lampOn() {
    System.err.println(phoneIp.getString() + ": Turning lamp on");
    Element root = new Element("LampOn");
    String sendingString = getSendingString(root);
    try {
      outToServer.writeBytes(sendingString + "\n\n");
      return true;
    }
    catch ( Exception e ) {
      System.err.println(e);
      return false;
    }
  }

  //Turn lamp off
  public boolean lampOff() {
    System.err.println (phoneIp.getString() + ": Turning lamp off");
    Element root = new Element("LampOff");
    String sendingString = getSendingString(root);
    try {
      outToServer.writeBytes(sendingString + "\n\n");
      return true;
    }
    catch ( Exception e ) {
      System.err.println(e);
      return false;
    }
  }

  //Ring phone
  public boolean startRinging() {
    System.err.println(phoneIp.getString() + ": Ringing");
    Element root = new Element("StartRinging");
    String sendingString = getSendingString(root);
    try {
      outToServer.writeBytes(sendingString + "\n\n");
      return true;
    }
    catch ( Exception e ) {
      System.err.println(e);
      return false;
    }
  }

  //Stop ringing phone
  public boolean stopRinging() {
    System.err.println(phoneIp.getString() + ": Stop ringing");
    Element root = new Element("StopRinging");
    String sendingString = getSendingString(root);
    try {
      outToServer.writeBytes(sendingString + "\n\n");
      return true;
    }catch ( Exception e ) {
      System.err.println(e);
      return false;
    }
  }

  //Play a tone with a given cadence
  public boolean playTone(Tone tone, Cadence cadence) {
    System.err.println(phoneIp.getString() + ": Played tone");
    Element root = new Element("PlayTone");
    root.addContent(new Element("Tone").addContent("" + tone.getType()));
    root.addContent(new Element("Cadence").addContent("" + cadence.getType()));
    String sendingString = getSendingString(root);
    try {
      outToServer.writeBytes(sendingString + "\n\n");
      return true;
    }catch ( Exception e ) {
      System.err.println(e);
      return false;
    }
  }

  //Display a string on a given line at a given position
  public boolean DisplayString(String lineStr, int lineNum, int linePos) {
    System.err.println(phoneIp.getString() + ": Displaying " + "\"" + lineStr + "\"");
    Element root = new Element("DisplayString");
    root.addContent(new Element("String").addContent(lineStr));
    root.addContent(new Element("lineNum").addContent("" + lineNum));
    root.addContent(new Element("linePos").addContent("" + linePos));
    String sendingString = getSendingString(root);
    try {
      outToServer.writeBytes(sendingString + "\n\n");
      return true;
    }catch ( Exception e ) {
      System.err.println(e);
      return false;
    }
  }

  //Append a string to the last string displayed
  public boolean AppendString(String lineStr) {
    System.err.println(phoneIp.getString() + ": Appending " + "\"" + lineStr + "\"");
    Element root = new Element("AppendString");
    root.addContent(new Element("String").addContent(lineStr));
    String sendingString = getSendingString(root);
    try {
      outToServer.writeBytes(sendingString + "\n\n");
      return true;
    }catch ( Exception e ) {
      System.err.println(e);
      return false;
    }
  }

  //Testing methods
  public boolean TestDigitPress(int digit) {
    System.err.println(phoneIp.getString() + ": " + digit +" pressed");
    Element root = new Element("TestDigitPressed");
    root.addContent(new Element("Digit").addContent("" + digit));
    String sendingString = getSendingString(root);
    try {
      outToServer.writeBytes(sendingString + "\n\n");
      return true;
    }
    catch ( Exception e ) {
      return false;
    }
  }
  public boolean TestDigitRelease(int digit) {
    System.err.println(phoneIp.getString() + ": " + digit +" released");
    Element root = new Element("TestDigitReleased");
    root.addContent(new Element("Digit").addContent("" + digit));
    String sendingString = getSendingString(root);
    try {
      outToServer.writeBytes(sendingString + "\n\n");
      return true;
    }
    catch ( Exception e ) {
      System.err.println(e);
      return false;
    }
  }
  public boolean TestOnHook() {
    System.err.println(phoneIp.getString() + ": On hook");
    Element root = new Element("TestOnHook");
    String sendingString = getSendingString(root);
    try {
      outToServer.writeBytes(sendingString + "\n\n");
      return true;
    }
    catch ( Exception e ) {
      return false;
    }
  }
  
  public boolean TestOffHook() {
    System.err.print(phoneIp.getString() + ": Off hook");
    Element root = new Element("TestOffHook");
    String sendingString = getSendingString(root);
    try {
      outToServer.writeBytes(sendingString + "\n\n");
      return true;
    }
    catch ( Exception e ) {
      return false;
    }
  }

  private String getSendingString(Element root) {
    Document doc = new Document(root);
    StringWriter writer = new StringWriter();
    XMLOutputter singleLine = null;
    try {
      singleLine = new XMLOutputter("", false);
      singleLine.output(root, writer);
    }
    catch ( java.io.IOException e ) {
      return null;
    }
    return writer.toString();
  }

  private Event interpreteXML(String xml) {
    try {
      SAXBuilder configBuilder = new SAXBuilder(XML_PARSER, false);
      StringReader reader = new StringReader(xml);
      Document configDoc = configBuilder.build(reader);
      Element root = configDoc.getRootElement();
      if ( root.getName().equals(Event.RESOURCE_ACQUIRED_TYPE) ) {
        Element resource = root.getChild("Resource");
        if ( resource != null ) {
          return Event.getResourceAcquiredEvent(resource.getText());
        }
        else {
          return Event.getResourceAcquiredEvent(null);
        }
      }
      else if ( root.getName().equals(Event.ERROR_TYPE) ) {
        Element errorDescription = root.getChild("ErrorDescription");
        if ( errorDescription != null ) {
          return Event.getErrorEvent(errorDescription.getText());
        }
        else {
          return Event.getErrorEvent(null);
        }
      }
      else if ( root.getName().equals(Event.ON_HOOK_TYPE) ) {
        return Event.ON_HOOK;
      }
      else if ( root.getName().equals(Event.OFF_HOOK_TYPE) ) {
        return Event.OFF_HOOK;
      }
      else if ( root.getName().equals(Event.DIGIT_PRESSED_TYPE) ) {
        Element value = root.getChild("Value");
        if ( value != null ) {
          return Event.getDigitPressedEvent(new Integer(value.getText()));
        }
        else {
          return Event.getDigitPressedEvent(null);
        }
      }
      else if ( root.getName().equals(Event.DIGIT_RELEASED_TYPE) ) {
        Element value = root.getChild("Value");
        if ( value != null ) {
          return Event.getDigitReleasedEvent(new Integer(value.getText()));
        }
        else {
          return Event.getDigitReleasedEvent(null);
        }
      }
      else {
        return Event.UNKNOWN;
      }
    }
    catch ( Exception e ) {
      e.printStackTrace();
      return null;
    }
  }

}
